<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=840488
-->
<window title="Mozilla Bug 840488"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>

  <!-- test results are displayed in the html:body -->
  <body xmlns="http://www.w3.org/1999/xhtml">
  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=840488"
     target="_blank">Mozilla Bug 840488</a>
  </body>

  <iframe id="root" name="root" type="content"/>
  <iframe id="chromeFrame" name="chromeFrame" type="content"/>

  <!-- test code goes here -->
  <script type="application/javascript">
  <![CDATA[

  /** Test for all the different ways that script can be disabled for a given global. **/

  SimpleTest.waitForExplicitFinish();
  const Cu = Components.utils;
  const Ci = Components.interfaces;
  Cu.import("resource://gre/modules/Promise.jsm");
  Cu.import("resource://gre/modules/Services.jsm");
  const ssm = Services.scriptSecurityManager;
  function makeURI(uri) { return Services.io.newURI(uri, null, null); }
  const path = "/tests/caps/tests/mochitest/file_disableScript.html";
  const uri = "http://www.example.com" + path;
  var rootFrame = document.getElementById('root');
  var chromeFrame = document.getElementById('chromeFrame');
  navigateFrame(rootFrame, uri + "?name=rootframe").then(function() {
    navigateFrame(chromeFrame, "file_disableScript.html").then(go);
  });

  function navigateFrame(ifr, src) {
    let deferred = Promise.defer();
    function onload() {
      ifr.removeEventListener('load', onload);
      deferred.resolve();
    }
    ifr.addEventListener('load', onload, false);
    ifr.setAttribute('src', src);
    return deferred.promise;
  }

  function navigateBack(ifr) {
    let deferred = Promise.defer();

    // pageshow events don't fire on the iframe element, so we need to use the
    // chrome event handler for the docshell.
    var browser = ifr.contentWindow
                     .QueryInterface(Ci.nsIInterfaceRequestor)
                     .getInterface(Ci.nsIWebNavigation)
                     .QueryInterface(Ci.nsIDocShell)
                     .chromeEventHandler;
    function onpageshow(evt) {
      info("Navigated back. Persisted: " + evt.persisted);
      browser.removeEventListener('pageshow', onpageshow);
      deferred.resolve();
    }
    browser.addEventListener('pageshow', onpageshow, false);
    ifr.contentWindow.history.back();
    return deferred.promise;
  }

  function addFrame(parentWin, name, expectOnload) {
    let ifr = parentWin.document.createElement('iframe');
    parentWin.document.body.appendChild(ifr);
    ifr.setAttribute('name', name);
    let deferred = Promise.defer();
    // We need to append 'name' to avoid running afoul of recursive frame detection.
    let frameURI = uri + "?name=" + name;
    navigateFrame(ifr, frameURI).then(function() {
      is(String(ifr.contentWindow.location), frameURI, "Successful load");
      is(!!ifr.contentWindow.wrappedJSObject.gFiredOnload, expectOnload,
         "onload should only fire when scripts are enabled");
      deferred.resolve();
    });
    return deferred.promise;
  }

  function checkScriptEnabled(win, expectEnabled) {
    win.wrappedJSObject.gFiredOnclick = false;
    win.document.body.dispatchEvent(new win.Event('click'));
    is(win.wrappedJSObject.gFiredOnclick, expectEnabled, "Checking script-enabled for " + win.name + " (" + win.location + ")");
  }

  function setScriptEnabledForDocShell(win, enabled) {
    win.QueryInterface(Ci.nsIInterfaceRequestor)
       .getInterface(Ci.nsIDocShell)
       .allowJavascript = enabled;
  }

  function testList(expectEnabled, win, list, idx) {
    idx = idx || 0;
    let deferred = Promise.defer();
    let target = list[idx] + path;
    info("Testing scriptability for: " + target + ". expecting " + expectEnabled);
    navigateFrame(win.frameElement, target).then(function() {
      checkScriptEnabled(win, expectEnabled);
      if (idx == list.length - 1)
        deferred.resolve();
      else
      testList(expectEnabled, win, list, idx + 1).then(function() { deferred.resolve(); });
    });
    return deferred.promise;
  }

  function testDomainPolicy(defaultScriptability, exceptions, superExceptions,
                            exempt, notExempt, set, superSet, win) {
    // Populate our sets.
    for (var e of exceptions)
      set.add(makeURI(e));
    for (var e of superExceptions)
      superSet.add(makeURI(e));

    return testList(defaultScriptability, win, notExempt).then(function() {
      return testList(!defaultScriptability, win, exempt);
    });
  }

  function setScriptEnabledForBrowser(enabled) {
    var prefname = "javascript.enabled";
    Services.prefs.setBoolPref(prefname, enabled);
  }

  function reloadFrame(frame) {
    let deferred = Promise.defer();
    frame.addEventListener('load', function onload() {
      deferred.resolve();
      frame.removeEventListener('load', onload);
    }, false);
    frame.contentWindow.location.reload(true);
    return deferred.promise;
  }

  function go() {
    var rootWin = rootFrame.contentWindow;
    var chromeWin = chromeFrame.contentWindow;

    // Test simple docshell enable/disable.
    checkScriptEnabled(rootWin, true);
    setScriptEnabledForDocShell(rootWin, false);
    checkScriptEnabled(rootWin, false);
    setScriptEnabledForDocShell(rootWin, true);
    checkScriptEnabled(rootWin, true);

    // Privileged frames are immune to docshell flags.
    ok(ssm.isSystemPrincipal(chromeWin.document.nodePrincipal), "Sanity check for System Principal");
    setScriptEnabledForDocShell(chromeWin, false);
    checkScriptEnabled(chromeWin, true);
    setScriptEnabledForDocShell(chromeWin, true);

    // Play around with the docshell tree and make sure everything works as
    // we expect.
    addFrame(rootWin, 'parent', true).then(function() {
      checkScriptEnabled(rootWin[0], true);
      return addFrame(rootWin[0], 'childA', true);
    }).then(function() {
      checkScriptEnabled(rootWin[0][0], true);
      setScriptEnabledForDocShell(rootWin[0], false);
      checkScriptEnabled(rootWin, true);
      checkScriptEnabled(rootWin[0], false);
      checkScriptEnabled(rootWin[0][0], false);
      return addFrame(rootWin[0], 'childB', false);
    }).then(function() {
      checkScriptEnabled(rootWin[0][1], false);
      setScriptEnabledForDocShell(rootWin[0][0], false);
      setScriptEnabledForDocShell(rootWin[0], true);
      checkScriptEnabled(rootWin[0], true);
      checkScriptEnabled(rootWin[0][0], false);
      setScriptEnabledForDocShell(rootWin[0][0], true);

      // Flags are inherited from the parent docshell at attach time. Note that
      // the flag itself is inherited, regardless of whether or not scripts are
      // currently allowed on the parent (which could depend on the parent's
      // parent). Check that.
      checkScriptEnabled(rootWin[0][1], false);
      setScriptEnabledForDocShell(rootWin[0], false);
      setScriptEnabledForDocShell(rootWin[0][1], true);
      return addFrame(rootWin[0][1], 'grandchild', false);
    }).then(function() {
      checkScriptEnabled(rootWin[0], false);
      checkScriptEnabled(rootWin[0][1], false);
      checkScriptEnabled(rootWin[0][1][0], false);
      setScriptEnabledForDocShell(rootWin[0], true);
      checkScriptEnabled(rootWin[0], true);
      checkScriptEnabled(rootWin[0][1], true);
      checkScriptEnabled(rootWin[0][1][0], true);

    // Try navigating two frames, then munging docshell scriptability, then
    // pulling the frames out of the bfcache to make sure that flags are
    // properly propagated to inactive inner windows. We do this both for an
    // 'own' docshell, as well as for an ancestor docshell.
      return navigateFrame(rootWin[0][0].frameElement, rootWin[0][0].location + '-navigated');
    }).then(function() { return navigateFrame(rootWin[0][1][0].frameElement, rootWin[0][1][0].location + '-navigated'); })
      .then(function() {
      checkScriptEnabled(rootWin[0][0], true);
      checkScriptEnabled(rootWin[0][1][0], true);
      setScriptEnabledForDocShell(rootWin[0][0], false);
      setScriptEnabledForDocShell(rootWin[0][1], false);
      checkScriptEnabled(rootWin[0][0], false);
      checkScriptEnabled(rootWin[0][1][0], false);
      return navigateBack(rootWin[0][0].frameElement);
    }).then(function() { return navigateBack(rootWin[0][1][0].frameElement); })
      .then(function() {
      checkScriptEnabled(rootWin[0][0], false);
      checkScriptEnabled(rootWin[0][1][0], false);

    // Disable JS via the global pref pref. This is only guaranteed to have an effect
    // for subsequent loads.
      setScriptEnabledForBrowser(false);
      return reloadFrame(rootFrame);
    }).then(function() {
      checkScriptEnabled(rootWin, false);
      checkScriptEnabled(chromeWin, true);
      setScriptEnabledForBrowser(true);
      return reloadFrame(rootFrame);
    }).then(function() {
      checkScriptEnabled(rootWin, true);

    // Play around with dynamically blocking script for a given global.
    // This takes effect immediately.
      Cu.blockScriptForGlobal(rootWin);
      Cu.blockScriptForGlobal(rootWin);
      Cu.unblockScriptForGlobal(rootWin);
      checkScriptEnabled(rootWin, false);
      Cu.unblockScriptForGlobal(rootWin);
      checkScriptEnabled(rootWin, true);
      Cu.blockScriptForGlobal(rootWin);
      try {
        Cu.blockScriptForGlobal(chromeWin);
        ok(false, "Should have thrown");
      } catch (e) {
        ok(/may not be disabled/.test(e),
           "Shouldn't be able to programmatically block script for system globals");
      }
      return reloadFrame(rootFrame);
    }).then(function() {
      checkScriptEnabled(rootWin, true);

    // Test system-wide domain policy. This only takes effect for subsequently-
    // loaded globals.

    // Check the basic semantics of the sets.
    is(ssm.domainPolicyActive, false, "not enabled");
    window.policy = ssm.activateDomainPolicy();
    ok(policy instanceof Ci.nsIDomainPolicy, "Got a policy");
    try {
      ssm.activateDomainPolicy();
      ok(false, "Should have thrown");
    } catch (e) {
      ok(true, "can't have two live domain policies");
    }
    var sbRef = policy.superBlacklist;
    isnot(sbRef, null, "superBlacklist non-null");
    ok(!sbRef.contains(makeURI('http://www.example.com')));
    sbRef.add(makeURI('http://www.example.com/foopy'));
    ok(sbRef.contains(makeURI('http://www.example.com')));
    sbRef.remove(makeURI('http://www.example.com'));
    ok(!sbRef.contains(makeURI('http://www.example.com')));
    sbRef.add(makeURI('http://www.example.com/foopy/this.that/'));
    ok(sbRef.contains(makeURI('http://www.example.com/baz')));
    ok(!sbRef.contains(makeURI('https://www.example.com')));
    ok(!sbRef.contains(makeURI('https://www.example.com:88')));
    ok(!sbRef.contains(makeURI('http://foo.www.example.com')));
    ok(sbRef.containsSuperDomain(makeURI('http://foo.www.example.com')));
    ok(sbRef.containsSuperDomain(makeURI('http://foo.bar.www.example.com')));
    ok(!sbRef.containsSuperDomain(makeURI('http://foo.bar.www.exxample.com')));
    ok(!sbRef.containsSuperDomain(makeURI('http://example.com')));
    ok(!sbRef.containsSuperDomain(makeURI('http://com/this.that/')));
    ok(!sbRef.containsSuperDomain(makeURI('https://foo.www.example.com')));
    ok(sbRef.contains(makeURI('http://www.example.com')));
    policy.deactivate();
    is(ssm.domainPolicyActive, false, "back to inactive");
    ok(!sbRef.contains(makeURI('http://www.example.com')),
       "Disabling domain policy clears the set");
    policy = ssm.activateDomainPolicy();
    ok(policy.superBlacklist);
    isnot(sbRef, policy.superBlacklist, "Mint new sets each time!");
    policy.deactivate();
    is(policy.blacklist, null, "blacklist nulled out");
    policy = ssm.activateDomainPolicy();
    isnot(policy.blacklist, null, "non-null again");
    isnot(policy.blacklist, sbRef, "freshly minted");
    policy.deactivate();

    //
    // Now, create and apply a mock-policy. We check the same policy both as
    // a blacklist and as a whitelist.
    //

    window.testPolicy = {
      // The policy.
      exceptions: ['http://test1.example.com', 'http://example.com'],
      superExceptions: ['http://test2.example.org', 'https://test1.example.com'],

      // The testcases.
      exempt: ['http://test1.example.com', 'http://example.com',
               'http://test2.example.org', 'http://sub1.test2.example.org',
               'https://sub1.test1.example.com'],

      notExempt: ['http://test2.example.com', 'http://sub1.test1.example.com',
                  'http://www.example.com', 'https://test2.example.com',
                  'https://example.com', 'http://test1.example.org'],
    };

    policy = ssm.activateDomainPolicy();
    info("Testing Blacklist-style Domain Policy");
    return testDomainPolicy(true, testPolicy.exceptions,
                            testPolicy.superExceptions, testPolicy.exempt,
                            testPolicy.notExempt, policy.blacklist,
                            policy.superBlacklist, rootWin);
  }).then(function() {
    policy.deactivate();
    policy = ssm.activateDomainPolicy();
    info("Testing Whitelist-style Domain Policy");
    setScriptEnabledForBrowser(false);
    return testDomainPolicy(false, testPolicy.exceptions,
                            testPolicy.superExceptions, testPolicy.exempt,
                            testPolicy.notExempt, policy.whitelist,
                            policy.superWhitelist, rootWin);
  }).then(function() {
    setScriptEnabledForBrowser(true);
    policy.deactivate();

    SimpleTest.finish();
    });
  }

  ]]>
  </script>
</window>
